package com.quhaodian.plug.data.service.impl;

import com.quhaodian.plug.api.StoragePlugin;
import com.quhaodian.plug.data.service.PluginService;
import com.quhaodian.plug.data.service.StorageService;
import com.quhaodian.plug.data.vo.FileInfo;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletContext;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.*;


@Service("storageServiceImpl")
public class StorageServiceImpl implements StorageService, ServletContextAware {
    /** servletContext */
    private ServletContext servletContext;

    @Resource(name = "pluginServiceImpl")
    private PluginService pluginService;

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    /**
     * 添加上传任务
     *
     * @param storagePlugin
     *            存储插件
     * @param path
     *            上传路径
     * @param tempFile
     *            临时文件
     * @param contentType
     *            文件类型
     */
    private void addTask(final StoragePlugin storagePlugin, final String path, final File tempFile, final String contentType) {
        Runnable runnable= new Runnable() {
            @Override
            public void run() {
                try {
                    storagePlugin.upload(path, tempFile, contentType);
                } finally {
                    FileUtils.deleteQuietly(tempFile);
                }
            }
        };
        new Thread(runnable).start();
    }

    @Override
    public boolean isValid(FileInfo.FileType fileType, MultipartFile multipartFile) {
        return false;
    }

    @Override
    public String upload(FileInfo.FileType fileType, MultipartFile multipartFile, boolean async) {
        if (multipartFile == null) {
            return null;
        }
        String uploadPath = getPath(fileType);
        try {
            Map<String, Object> model = new HashMap<String, Object>();
            model.put("uuid", UUID.randomUUID().toString());
            String path = process(uploadPath, model);
            String destPath = path + UUID.randomUUID() + "." + FilenameUtils.getExtension(multipartFile.getOriginalFilename());

            for (StoragePlugin storagePlugin : pluginService.getStoragePlugins(true)) {
                File tempFile = new File(System.getProperty("java.io.tmpdir") + "/upload_" + UUID.randomUUID() + ".tmp");
                if (!tempFile.getParentFile().exists()) {
                    tempFile.getParentFile().mkdirs();
                }
                multipartFile.transferTo(tempFile);
                if (async) {
                    addTask(storagePlugin, destPath, tempFile, multipartFile.getContentType());
                } else {
                    try {
                        storagePlugin.upload(destPath, tempFile, multipartFile.getContentType());
                    } finally {
                        FileUtils.deleteQuietly(tempFile);
                    }
                }
                return storagePlugin.getUrl(destPath);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getPath(FileInfo.FileType fileType) {
        String uploadPath;
        if (fileType == FileInfo.FileType.flash) {
            uploadPath = "/upload/flash/${.now?string('yyyyMM')}/";
        } else if (fileType == FileInfo.FileType.media) {
            uploadPath = "/upload/media/${.now?string('yyyyMM')}/";
        } else if (fileType == FileInfo.FileType.file) {
            uploadPath ="/upload/file/${.now?string('yyyyMM')}/";
        } else {
            uploadPath = "/upload/image/${.now?string('yyyyMM')}/";
        }
        return uploadPath;
    }

    @Override
    public String upload(FileInfo.FileType fileType, MultipartFile multipartFile) {
        return upload(fileType, multipartFile, false);
    }

    @Override
    public String uploadLocal(FileInfo.FileType fileType, MultipartFile multipartFile) {
        if (multipartFile == null) {
            return null;
        }
        String uploadPath = getPath(fileType);
        try {
            Map<String, Object> model = new HashMap<String, Object>();
            model.put("uuid", UUID.randomUUID().toString());
            String path = process(uploadPath, model);
            String destPath = path + UUID.randomUUID() + "." + FilenameUtils.getExtension(multipartFile.getOriginalFilename());
            File destFile = new File(servletContext.getRealPath(destPath));
            if (!destFile.getParentFile().exists()) {
                destFile.getParentFile().mkdirs();
            }
            multipartFile.transferTo(destFile);
            return destPath;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private String process(String uploadPath, Map<String, Object> model) {
        if (uploadPath == null) {
            return null;
        }
        Configuration      configuration = new Configuration();
        StringWriter out = new StringWriter();
        try {
            new Template("template", new StringReader(uploadPath), configuration).process(model, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return out.toString();
    }

    @Override
    public List<FileInfo> browser(String path, FileInfo.FileType fileType, FileInfo.OrderType orderType) {
        if (path != null) {
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (!path.endsWith("/")) {
                path += "/";
            }
        } else {
            path = "/";
        }
        String uploadPath=getPath(fileType);
        String browsePath = StringUtils.substringBefore(uploadPath, "${");
        browsePath = StringUtils.substringBeforeLast(browsePath, "/") + path;

        List<FileInfo> fileInfos = new ArrayList<FileInfo>();
        if (browsePath.indexOf("..") >= 0) {
            return fileInfos;
        }
        for (StoragePlugin storagePlugin : pluginService.getStoragePlugins(true)) {
            fileInfos = storagePlugin.browser(browsePath);
            break;
        }
        if (orderType == FileInfo.OrderType.size) {
            Collections.sort(fileInfos, new SizeComparator());
        } else if (orderType == FileInfo.OrderType.type) {
            Collections.sort(fileInfos, new TypeComparator());
        } else {
            Collections.sort(fileInfos, new NameComparator());
        }
        return fileInfos;
    }

    private class NameComparator implements Comparator<FileInfo> {

        @Override
        public int compare(FileInfo fileInfos1, FileInfo fileInfos2) {
            return new CompareToBuilder().append(!fileInfos1.getIsDirectory(), !fileInfos2.getIsDirectory()).append(fileInfos1.getName(), fileInfos2.getName()).toComparison();
        }
    }

    private class SizeComparator implements Comparator<FileInfo> {
        @Override
        public int compare(FileInfo fileInfos1, FileInfo fileInfos2) {
            return new CompareToBuilder().append(!fileInfos1.getIsDirectory(), !fileInfos2.getIsDirectory()).append(fileInfos1.getSize(), fileInfos2.getSize()).toComparison();
        }
    }

    private class TypeComparator implements Comparator<FileInfo> {

        @Override
        public int compare(FileInfo fileInfos1, FileInfo fileInfos2) {
            return new CompareToBuilder().append(!fileInfos1.getIsDirectory(), !fileInfos2.getIsDirectory()).append(FilenameUtils.getExtension(fileInfos1.getName()), FilenameUtils.getExtension(fileInfos2.getName())).toComparison();
        }
    }
}
